package com.espirit.eap.conversion;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.espirit.eap.pagelayout.Element;
import com.espirit.eap.pagelayout.ElementService;
import com.espirit.eap.pagelayout.Layout;
import com.espirit.eap.pagelayout.Page;
import com.espirit.eap.util.TaglibManager;
import com.espirit.eap.util.TxFileService;
import com.espirit.eap.util.tagAdaptor.TagAdaptorUtil;
import com.googlecode.cswish.model.PageElements;
import com.googlecode.cswish.struts.hibernate.HibernateUtil;

public class ConversionContext {

	private ElementService elementService;
	private TaglibManager tagManager;
	private TagAdaptorUtil tagAdaptorUtil;
	private TxFileService txFileService;
	private boolean generateDebugInfo;
	
	private Page page;
	private StringBuilder layoutContent;
	private Map<String, Object> context;
	private Set<String> tagCategories;
	private Layout root;
	
	private PageElements pageElements;
	
	// the flag of self-defined page
	private boolean isPageDefine;
	private boolean isDebug;
	
	private Map<Object, String> categoryClass = new IdentityHashMap<>();
	private Map<Element, String> exCategoryClass = new IdentityHashMap<>();
	private List<ReferencePagePosition> referencePages;

	public ConversionContext(ElementService elementService,
			TaglibManager tagManager, TagAdaptorUtil tagAdaptorUtil, TxFileService txFileService,
			Map<String, Object> context, boolean generateDebugInfo, Page page, 
			boolean isPageDefine, boolean isDebug, PageElements pageElements) {
		this.elementService = elementService;
		this.tagManager = tagManager;
		this.tagAdaptorUtil = tagAdaptorUtil;
		this.txFileService = txFileService;
		
		this.page = page;
		this.context = context;
		this.generateDebugInfo = generateDebugInfo;
		this.root = (Layout) HibernateUtil.unproxy(page.getLayout());
		this.isPageDefine = isPageDefine;
		this.isDebug = isDebug;
		
		this.pageElements = pageElements;

		this.layoutContent = new StringBuilder();
		this.tagCategories = new HashSet<String>();
	}

	public ElementService getElementService() {
		return elementService;
	}

	public void setElementService(ElementService elementService) {
		this.elementService = elementService;
	}

	public TaglibManager getTagManager() {
		return tagManager;
	}

	public void setTagManager(TaglibManager tagManager) {
		this.tagManager = tagManager;
	}

	public TxFileService getTxFileService() {
		return txFileService;
	}

	public void setTxFileService(TxFileService txFileService) {
		this.txFileService = txFileService;
	}

	public Page getPage() {
		return page;
	}

	public void setPage(Page page) {
		this.page = page;
	}

	public boolean isGenerateDebugInfo() {
		return generateDebugInfo;
	}

	public void setGenerateDebugInfo(boolean generateDebugInfo) {
		this.generateDebugInfo = generateDebugInfo;
	}

	public StringBuilder getLayoutContent() {
		return layoutContent;
	}

	public void setLayoutContent(StringBuilder layoutContent) {
		this.layoutContent = layoutContent;
	}

	public Map<String, Object> getContext() {
		return context;
	}

	public void setContext(Map<String, Object> context) {
		this.context = context;
	}

	public Set<String> getTagCategories() {
		return tagCategories;
	}

	public Layout getRoot() {
		return root;
	}

	public void setRoot(Layout root) {
		this.root = root;
	}

	public boolean isPageDefine() {
		return isPageDefine;
	}

	public void setPageDefine(boolean isPageDefine) {
		this.isPageDefine = isPageDefine;
	}

	public boolean isDebug() {
		return isDebug;
	}

	public void setDebug(boolean isDebug) {
		this.isDebug = isDebug;
	}

	public PageElements getRootPageElements() {
		return pageElements;
	}

	public String getCategoryClass(Object element) {
		return categoryClass.get(element);
	}

	public void addCategoryClass(Object element, String clazz) {
		String oldClazz = categoryClass.get(element);
		String newClazz = mergeClass(oldClazz, clazz);
		if (newClazz != oldClazz) {		// fast check
			categoryClass.put(element, newClazz);
		}
	}
	
	public Map<Element, String> getExCategoryClass() {
		return exCategoryClass;
	}

	public void addExCategoryClass(Element element, String clazz) {
		String oldClazz = exCategoryClass.get(element);
		String newClazz = mergeClass(oldClazz, clazz);
		if (newClazz != oldClazz) {		// fast check
			exCategoryClass.put(element, newClazz);
		}
	}
	
	// TODO: merge with Page.addClazz?
	public String mergeClass(String oldClazz, String newClazz) {
		if (newClazz == null || newClazz.length() == 0 || newClazz.equals(oldClazz)) {
			// do nothing
		} else if (oldClazz == null || oldClazz.length() == 0) {
			oldClazz = newClazz;
		} else {
			String[] oldClasses = oldClazz.split(" ");
			for (String oldItem : oldClasses) {
				if (oldItem.equals(newClazz)) {
					return oldClazz;
				}
			}
			oldClazz = oldClazz + ' ' + newClazz;
		}
		return oldClazz;
	}
	
	private class ReferencePagePosition {
		private int posBegin;		// begin from 0
		private int posEnd;			// end with length
		private String url;
		
		private ReferencePagePosition(String url, int posBegin, int posEnd) {
			this.url = url;
			this.posBegin = posBegin;
			this.posEnd = posEnd;
		}
	}
	
	public void addReferencePage(String url, int posBegin, int posEnd) {
		if (url == null || url.length() == 0) {
			return;
		}
		if (referencePages == null) {
			referencePages = new ArrayList<>();
		}
		referencePages.add(new ReferencePagePosition(url, posBegin, posEnd));
	}

	public class TagPosition {
		public int openTagPos = -1;
		public int closeTagPos = -1;
		
		public boolean found() {
			return openTagPos != -1 && closeTagPos != -1;
		}
	}
	
	public class PagePosition {
		public TagPosition bodyTag;
		public TagPosition headerFooterTag;
		public TagPosition formTag;
		
		public PagePosition(TagPosition bodyTag, TagPosition headerFooterTag, TagPosition formTag) {
			this.bodyTag = bodyTag;
			this.headerFooterTag = headerFooterTag;
			this.formTag = formTag;
		}
		
		public boolean found() {
			return bodyTag.found() && headerFooterTag.found() && formTag.found();
		}
	}
	
	public PagePosition fastParse() {
		TagPosition bodyTag = new TagPosition();
		TagPosition headerFooterTag = new TagPosition();
		TagPosition formTag = new TagPosition();
		PagePosition pagePosition = new PagePosition(bodyTag, headerFooterTag, formTag);
		
		indexOf(layoutContent, "<body", "</body>", bodyTag);
		indexOf(layoutContent, "<@header", "<@footer/>", headerFooterTag);
		indexOf(layoutContent, "<@form", "</@form>", formTag);

		if (referencePages != null && !pagePosition.found()) {
			for (ReferencePagePosition refPagePosition : referencePages) {
				String template = getTxFileService().read(null, refPagePosition.url);
				
				if (template != null) {
					indexOf(template, refPagePosition.posBegin, refPagePosition.posEnd, "<body", "</body>", bodyTag);
					indexOf(template, refPagePosition.posBegin, refPagePosition.posEnd, "<@header", "<@footer/>", headerFooterTag);
					if (pagePosition.found()) {
						break;
					}
				}
			}
		}
		return pagePosition;
	}
	
	private void indexOf(String content, int offsetBegin, int offsetEnd, String openFlag, String closeFlag, TagPosition position) {
		int index = -1;
		if (position.openTagPos == -1) {
			index = content.indexOf(openFlag);
			if (index >= 0) {
				index = content.indexOf('>', index + 1);
				if (index != -1) {
					position.openTagPos = offsetEnd;
				}
			}
		}
		
		if (position.openTagPos != -1) {
			index = content.indexOf(closeFlag, index + 1);
			if (index != -1) {
				position.closeTagPos = offsetBegin;
				if (position.closeTagPos < position.openTagPos) {
					position.closeTagPos = position.openTagPos;
				}
			}
		}
	}
	
	private void indexOf(StringBuilder content, String openFlag, String closeFlag, TagPosition position) {
		int index = -1;
		if (position.openTagPos == -1) {
			index = content.indexOf(openFlag);
			if (index >= 0) {
				index = content.indexOf(">", index + 1);
				position.openTagPos = index + 1;
			}
		}
		
		if (position.openTagPos != -1) {
			position.closeTagPos = content.indexOf(closeFlag, index + 1);
		}
	}

	public TagAdaptorUtil getTagAdaptorUtil() {
		return tagAdaptorUtil;
	}
}